Skip to content

Conversation

@dpaoliello
Copy link
Contributor

@dpaoliello dpaoliello commented Nov 19, 2024

In MSVC, when /d1initall is enabled, __declspec(no_init_all) can be applied to a type to suppress auto-initialization for all instances of that type or to a function to suppress auto-initialization for all locals within that function.

This change does the same for Clang, except that it applies to the -ftrivial-auto-var-init flag instead.

NOTE: I did not add a Clang-specific spelling for this but would be happy to make a followup PR if folks are interested in that.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. labels Nov 19, 2024
@llvmbot
Copy link
Member

llvmbot commented Nov 19, 2024

@llvm/pr-subscribers-clang

Author: Daniel Paoliello (dpaoliello)

Changes

In MSVC, when /d1initall is enabled, __declspec(no_init_all) can be applied to a type to suppress auto-initialization for all instances of that type or to a function to suppress auto-initialization for all locals within that function.

This change does the same for Clang, except that it applies to the -ftrivial-auto-var-init flag instead.

NOTE:

  • I did not add a Clang-specific spelling for this but would be happy to make a followup PR if folks are interested in that.
  • Since this is implementing a __declspec only, I chose to leave it undocumented.

Full diff: https://github.com/llvm/llvm-project/pull/116847.diff

3 Files Affected:

  • (modified) clang/include/clang/Basic/Attr.td (+7)
  • (modified) clang/lib/CodeGen/CGDecl.cpp (+12-7)
  • (added) clang/test/CodeGenCXX/auto-var-init-attr.cpp (+59)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 6035a563d5fce7..5d3270280c17f6 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4888,3 +4888,10 @@ def ClspvLibclcBuiltin: InheritableAttr {
   let Documentation = [ClspvLibclcBuiltinDoc];
   let SimpleHandler = 1;
 }
+
+def NoTrivialAutoVarInit: InheritableAttr {
+  let Spellings = [Declspec<"no_init_all">];
+  let Subjects = SubjectList<[Function, Tag]>;
+  let Documentation = [Undocumented];
+  let SimpleHandler = 1;
+}
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index 6e9d28cea28e79..ff53ffae4459af 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -1900,10 +1900,16 @@ void CodeGenFunction::EmitAutoVarInit(const AutoVarEmission &emission) {
       locIsByrefHeader ? emission.getObjectAddress(*this) : emission.Addr;
 
   // Note: constexpr already initializes everything correctly.
+  auto typeHasNoTrivialAutoVarInitAttr = [&]() {
+    auto *TD = type->getAsTagDecl();
+    return TD && TD->hasAttr<NoTrivialAutoVarInitAttr>();
+  };
   LangOptions::TrivialAutoVarInitKind trivialAutoVarInit =
       (D.isConstexpr()
            ? LangOptions::TrivialAutoVarInitKind::Uninitialized
-           : (D.getAttr<UninitializedAttr>()
+           : ((D.getAttr<UninitializedAttr>() ||
+               typeHasNoTrivialAutoVarInitAttr() ||
+               CurFuncDecl->hasAttr<NoTrivialAutoVarInitAttr>())
                   ? LangOptions::TrivialAutoVarInitKind::Uninitialized
                   : getContext().getLangOpts().getTrivialAutoVarInit()));
 
@@ -1944,13 +1950,13 @@ void CodeGenFunction::EmitAutoVarInit(const AutoVarEmission &emission) {
                                   replaceUndef(CGM, isPattern, constant));
     }
 
-    if (constant && D.getType()->isBitIntType() &&
-        CGM.getTypes().typeRequiresSplitIntoByteArray(D.getType())) {
+    if (constant && type->isBitIntType() &&
+        CGM.getTypes().typeRequiresSplitIntoByteArray(type)) {
       // Constants for long _BitInt types are split into individual bytes.
       // Try to fold these back into an integer constant so it can be stored
       // properly.
-      llvm::Type *LoadType = CGM.getTypes().convertTypeForLoadStore(
-          D.getType(), constant->getType());
+      llvm::Type *LoadType =
+          CGM.getTypes().convertTypeForLoadStore(type, constant->getType());
       constant = llvm::ConstantFoldLoadFromConst(
           constant, LoadType, llvm::APInt::getZero(32), CGM.getDataLayout());
     }
@@ -1967,8 +1973,7 @@ void CodeGenFunction::EmitAutoVarInit(const AutoVarEmission &emission) {
       // It may be that the Init expression uses other uninitialized memory,
       // but auto-var-init here would not help, as auto-init would get
       // overwritten by Init.
-      if (!D.getType()->isScalarType() || capturedByInit ||
-          isAccessedBy(D, Init)) {
+      if (!type->isScalarType() || capturedByInit || isAccessedBy(D, Init)) {
         initializeWhatIsTechnicallyUninitialized(Loc);
       }
     }
diff --git a/clang/test/CodeGenCXX/auto-var-init-attr.cpp b/clang/test/CodeGenCXX/auto-var-init-attr.cpp
new file mode 100644
index 00000000000000..5481c6e8613c56
--- /dev/null
+++ b/clang/test/CodeGenCXX/auto-var-init-attr.cpp
@@ -0,0 +1,59 @@
+// RUN: %clang_cc1 -std=c++14 -triple x86_64-unknown-unknown -fblocks -fdeclspec -ftrivial-auto-var-init=zero %s -emit-llvm -o - | FileCheck %s
+
+struct S { char c; };
+class C { char c; };
+enum class E { ZERO };
+union U { char c; int i; };
+
+struct __declspec(no_init_all) NoInitS { char c; };
+class __declspec(no_init_all) NoInitC { char c; };
+enum class __declspec(no_init_all) NoInitE { ZERO };
+union __declspec(no_init_all) NoInitU { char c; int i; };
+
+extern "C" {
+  void test_no_attr() {
+    // CHECK-LABEL: @test_no_attr()
+    // CHECK-NEXT:  entry:
+    // CHECK-NEXT:  %s = alloca %struct.S, align 1
+    // CHECK-NEXT:  %c = alloca %class.C, align 1
+    // CHECK-NEXT:  %e = alloca i32, align 4
+    // CHECK-NEXT:  %u = alloca %union.U, align 4
+    // CHECK-NEXT:  call void @llvm.memset.p0.i64(ptr align 1 %s, i8 0, i64 1, i1 false)
+    // CHECK-NEXT:  call void @llvm.memset.p0.i64(ptr align 1 %c, i8 0, i64 1, i1 false)
+    // CHECK-NEXT:  store i32 0, ptr %e, align 4
+    // CHECK-NEXT:  call void @llvm.memset.p0.i64(ptr align 4 %u, i8 0, i64 4, i1 false)
+    // CHECK-NEXT   ret void
+    S s;
+    C c;
+    E e;
+    U u;
+  }
+
+  void __declspec(no_init_all) test_attr_on_function() {
+    // CHECK-LABEL: @test_attr_on_function()
+    // CHECK-NEXT:  entry:
+    // CHECK-NEXT:  %s = alloca %struct.S, align 1
+    // CHECK-NEXT:  %c = alloca %class.C, align 1
+    // CHECK-NEXT:  %e = alloca i32, align 4
+    // CHECK-NEXT:  %u = alloca %union.U, align 4
+    // CHECK-NEXT:  ret void
+    S s;
+    C c;
+    E e;
+    U u;
+  }
+
+  void test_attr_on_decl() {
+    // CHECK-LABEL: @test_attr_on_decl()
+    // CHECK-NEXT:  entry:
+    // CHECK-NEXT:  %s = alloca %struct.NoInitS, align 1
+    // CHECK-NEXT:  %c = alloca %class.NoInitC, align 1
+    // CHECK-NEXT:  %e = alloca i32, align 4
+    // CHECK-NEXT:  %u = alloca %union.NoInitU, align 4
+    // CHECK-NEXT:  ret void
+    NoInitS s;
+    NoInitC c;
+    NoInitE e;
+    NoInitU u;
+  }
+}
\ No newline at end of file

@llvmbot
Copy link
Member

llvmbot commented Nov 19, 2024

@llvm/pr-subscribers-clang-codegen

Author: Daniel Paoliello (dpaoliello)

Changes

In MSVC, when /d1initall is enabled, __declspec(no_init_all) can be applied to a type to suppress auto-initialization for all instances of that type or to a function to suppress auto-initialization for all locals within that function.

This change does the same for Clang, except that it applies to the -ftrivial-auto-var-init flag instead.

NOTE:

  • I did not add a Clang-specific spelling for this but would be happy to make a followup PR if folks are interested in that.
  • Since this is implementing a __declspec only, I chose to leave it undocumented.

Full diff: https://github.com/llvm/llvm-project/pull/116847.diff

3 Files Affected:

  • (modified) clang/include/clang/Basic/Attr.td (+7)
  • (modified) clang/lib/CodeGen/CGDecl.cpp (+12-7)
  • (added) clang/test/CodeGenCXX/auto-var-init-attr.cpp (+59)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 6035a563d5fce7..5d3270280c17f6 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4888,3 +4888,10 @@ def ClspvLibclcBuiltin: InheritableAttr {
   let Documentation = [ClspvLibclcBuiltinDoc];
   let SimpleHandler = 1;
 }
+
+def NoTrivialAutoVarInit: InheritableAttr {
+  let Spellings = [Declspec<"no_init_all">];
+  let Subjects = SubjectList<[Function, Tag]>;
+  let Documentation = [Undocumented];
+  let SimpleHandler = 1;
+}
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index 6e9d28cea28e79..ff53ffae4459af 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -1900,10 +1900,16 @@ void CodeGenFunction::EmitAutoVarInit(const AutoVarEmission &emission) {
       locIsByrefHeader ? emission.getObjectAddress(*this) : emission.Addr;
 
   // Note: constexpr already initializes everything correctly.
+  auto typeHasNoTrivialAutoVarInitAttr = [&]() {
+    auto *TD = type->getAsTagDecl();
+    return TD && TD->hasAttr<NoTrivialAutoVarInitAttr>();
+  };
   LangOptions::TrivialAutoVarInitKind trivialAutoVarInit =
       (D.isConstexpr()
            ? LangOptions::TrivialAutoVarInitKind::Uninitialized
-           : (D.getAttr<UninitializedAttr>()
+           : ((D.getAttr<UninitializedAttr>() ||
+               typeHasNoTrivialAutoVarInitAttr() ||
+               CurFuncDecl->hasAttr<NoTrivialAutoVarInitAttr>())
                   ? LangOptions::TrivialAutoVarInitKind::Uninitialized
                   : getContext().getLangOpts().getTrivialAutoVarInit()));
 
@@ -1944,13 +1950,13 @@ void CodeGenFunction::EmitAutoVarInit(const AutoVarEmission &emission) {
                                   replaceUndef(CGM, isPattern, constant));
     }
 
-    if (constant && D.getType()->isBitIntType() &&
-        CGM.getTypes().typeRequiresSplitIntoByteArray(D.getType())) {
+    if (constant && type->isBitIntType() &&
+        CGM.getTypes().typeRequiresSplitIntoByteArray(type)) {
       // Constants for long _BitInt types are split into individual bytes.
       // Try to fold these back into an integer constant so it can be stored
       // properly.
-      llvm::Type *LoadType = CGM.getTypes().convertTypeForLoadStore(
-          D.getType(), constant->getType());
+      llvm::Type *LoadType =
+          CGM.getTypes().convertTypeForLoadStore(type, constant->getType());
       constant = llvm::ConstantFoldLoadFromConst(
           constant, LoadType, llvm::APInt::getZero(32), CGM.getDataLayout());
     }
@@ -1967,8 +1973,7 @@ void CodeGenFunction::EmitAutoVarInit(const AutoVarEmission &emission) {
       // It may be that the Init expression uses other uninitialized memory,
       // but auto-var-init here would not help, as auto-init would get
       // overwritten by Init.
-      if (!D.getType()->isScalarType() || capturedByInit ||
-          isAccessedBy(D, Init)) {
+      if (!type->isScalarType() || capturedByInit || isAccessedBy(D, Init)) {
         initializeWhatIsTechnicallyUninitialized(Loc);
       }
     }
diff --git a/clang/test/CodeGenCXX/auto-var-init-attr.cpp b/clang/test/CodeGenCXX/auto-var-init-attr.cpp
new file mode 100644
index 00000000000000..5481c6e8613c56
--- /dev/null
+++ b/clang/test/CodeGenCXX/auto-var-init-attr.cpp
@@ -0,0 +1,59 @@
+// RUN: %clang_cc1 -std=c++14 -triple x86_64-unknown-unknown -fblocks -fdeclspec -ftrivial-auto-var-init=zero %s -emit-llvm -o - | FileCheck %s
+
+struct S { char c; };
+class C { char c; };
+enum class E { ZERO };
+union U { char c; int i; };
+
+struct __declspec(no_init_all) NoInitS { char c; };
+class __declspec(no_init_all) NoInitC { char c; };
+enum class __declspec(no_init_all) NoInitE { ZERO };
+union __declspec(no_init_all) NoInitU { char c; int i; };
+
+extern "C" {
+  void test_no_attr() {
+    // CHECK-LABEL: @test_no_attr()
+    // CHECK-NEXT:  entry:
+    // CHECK-NEXT:  %s = alloca %struct.S, align 1
+    // CHECK-NEXT:  %c = alloca %class.C, align 1
+    // CHECK-NEXT:  %e = alloca i32, align 4
+    // CHECK-NEXT:  %u = alloca %union.U, align 4
+    // CHECK-NEXT:  call void @llvm.memset.p0.i64(ptr align 1 %s, i8 0, i64 1, i1 false)
+    // CHECK-NEXT:  call void @llvm.memset.p0.i64(ptr align 1 %c, i8 0, i64 1, i1 false)
+    // CHECK-NEXT:  store i32 0, ptr %e, align 4
+    // CHECK-NEXT:  call void @llvm.memset.p0.i64(ptr align 4 %u, i8 0, i64 4, i1 false)
+    // CHECK-NEXT   ret void
+    S s;
+    C c;
+    E e;
+    U u;
+  }
+
+  void __declspec(no_init_all) test_attr_on_function() {
+    // CHECK-LABEL: @test_attr_on_function()
+    // CHECK-NEXT:  entry:
+    // CHECK-NEXT:  %s = alloca %struct.S, align 1
+    // CHECK-NEXT:  %c = alloca %class.C, align 1
+    // CHECK-NEXT:  %e = alloca i32, align 4
+    // CHECK-NEXT:  %u = alloca %union.U, align 4
+    // CHECK-NEXT:  ret void
+    S s;
+    C c;
+    E e;
+    U u;
+  }
+
+  void test_attr_on_decl() {
+    // CHECK-LABEL: @test_attr_on_decl()
+    // CHECK-NEXT:  entry:
+    // CHECK-NEXT:  %s = alloca %struct.NoInitS, align 1
+    // CHECK-NEXT:  %c = alloca %class.NoInitC, align 1
+    // CHECK-NEXT:  %e = alloca i32, align 4
+    // CHECK-NEXT:  %u = alloca %union.NoInitU, align 4
+    // CHECK-NEXT:  ret void
+    NoInitS s;
+    NoInitC c;
+    NoInitE e;
+    NoInitU u;
+  }
+}
\ No newline at end of file

@efriedma-quic
Copy link
Collaborator

If we're going to treat /d1initall as an alias for -ftrivial-auto-var-init, can we just treat __declspec(no_init_all) as an alias for __attribute__((uninitialized))?

@dpaoliello
Copy link
Contributor Author

If we're going to treat /d1initall as an alias for -ftrivial-auto-var-init, can we just treat __declspec(no_init_all) as an alias for __attribute__((uninitialized))?

uninitialized currently only applied to local variable declarations, whereas __declspec(no_init_all) is for types and functions.

I think allowing uninitialized to be applied to types and functions would be confusing:

  • Does uninitialized on a non-trivial type suppress the normal automatic initialization? If not, then does this need a warning/error?
  • How is a user supposed to interpret uninitialized on a function? There's no obvious connection that it's referring to the local variables in the function, let alone those that are not automatically initialized.

This is why I wanted to avoid adding a Clang spelling: it feels like it would need a bunch of naming discussions and design work.

@efriedma-quic
Copy link
Collaborator

I see. I agree we don't need to add non-__declspec syntax here. We're adding this for MSVC compat, so we need to match MSVC semantics anyway, so there's not really any other way to implement this. We can always adjust the code later if we end up implementing an overlapping feature for non-MSVC targets.

I'd still like to have a basic description in documentation for reference, especially since there isn't any corresponding MSVC compiler documentation.

@dpaoliello dpaoliello force-pushed the noautoinit branch 2 times, most recently from bacaeb4 to d97c75c Compare November 19, 2024 23:28
@dpaoliello
Copy link
Contributor Author

I'd still like to have a basic description in documentation for reference, especially since there isn't any corresponding MSVC compiler documentation.

Done

a marked type. Note that this attribute has no effect for locals that are automatically initialized
without the `-ftrivial-auto-var-init`_ flag.

.. _`-ftrivial-auto-var-init`: https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-ftrivial-auto-var-init
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you write something like the following?

:ref:`option:: -ftrivial-auto-var-init=<arg>`

Not a big deal, but ideally we want people to be able to publish documentation to places other than clang.llvm.org.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made the link relative, but I have no idea how to get the :ref: syntax to work.

In theory it's a link to an option: https://www.sphinx-doc.org/en/master/usage/referencing.html#role-option, but it's also cross-doc, so :option:`-ftrivial-auto-var-init <ClangCommandLineReference:-ftrivial-auto-var-init=<arg>>` should work, but it didn't seem to.

Copy link
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@dpaoliello dpaoliello merged commit c86899d into llvm:main Nov 21, 2024
9 checks passed
@dpaoliello dpaoliello deleted the noautoinit branch November 21, 2024 00:48
HBelusca added a commit to HBelusca/reactos that referenced this pull request Oct 26, 2025
Addendum to commit 73b54ce.

Conditionally define it on the `_MSC_VER >= 1915, and add the other
`MIDL_PASS` and co. guard checks as in the official Windows PSDK.

Additionally, no-op the define for Clang to silence the following
warnings:
```
sdk\include\ddk\ntddk.h(2050,35): warning: __declspec attribute 'no_init_all' is not supported [-Wignored-attributes]
sdk\include\psdk\ntdef.h(40,95): note: expanded from macro 'DECLSPEC_NOINITALL'
```
Granted, this may be due to the fact our GitHub actions currently use
Clang 13.0.1:
```
-- The C compiler identification is Clang 13.0.1 with MSVC-like command-line
-- The CXX compiler identification is Clang 13.0.1 with MSVC-like command-line
```
while support for `no_init_all` may have been added for Clang 22.0.0,
if https://clang.llvm.org/docs/AttributeReference.html#no-init-all is correct.
(See PR llvm/llvm-project#116847 )
HBelusca added a commit to HBelusca/reactos that referenced this pull request Oct 26, 2025
Addendum to commit 73b54ce.

Conditionally define it on the `_MSC_VER >= 1915, and add the other
`MIDL_PASS` and co. guard checks as in the official Windows PSDK.

Additionally, no-op the define for Clang to silence the following
warnings:
```
sdk\include\ddk\ntddk.h(2050,35): warning: __declspec attribute 'no_init_all' is not supported [-Wignored-attributes]
sdk\include\psdk\ntdef.h(40,95): note: expanded from macro 'DECLSPEC_NOINITALL'
```
Granted, this may be due to the fact our GitHub actions currently use
Clang 13.0.1:
```
-- The C compiler identification is Clang 13.0.1 with MSVC-like command-line
-- The CXX compiler identification is Clang 13.0.1 with MSVC-like command-line
```
while support for `no_init_all` may have been added for Clang 22.0.0,
if https://clang.llvm.org/docs/AttributeReference.html#no-init-all is correct.
(See PR llvm/llvm-project#116847 )
HBelusca added a commit to HBelusca/reactos that referenced this pull request Nov 6, 2025
Addendum to commit 73b54ce.

Conditionally define it on the `_MSC_VER >= 1915, and add the other
`MIDL_PASS` and co. guard checks as in the official Windows PSDK.

Additionally, no-op the define for Clang to silence the following
warnings:
```
sdk\include\ddk\ntddk.h(2050,35): warning: __declspec attribute 'no_init_all' is not supported [-Wignored-attributes]
sdk\include\psdk\ntdef.h(40,95): note: expanded from macro 'DECLSPEC_NOINITALL'
```
Granted, this may be due to the fact our GitHub actions currently use
Clang 13.0.1:
```
-- The C compiler identification is Clang 13.0.1 with MSVC-like command-line
-- The CXX compiler identification is Clang 13.0.1 with MSVC-like command-line
```
while support for `no_init_all` may have been added for Clang 22.0.0,
if https://clang.llvm.org/docs/AttributeReference.html#no-init-all is correct.
(See PR llvm/llvm-project#116847 )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category extension:microsoft

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants